package com.walkthecow.justdraw;

import java.util.ArrayList;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BlurMaskFilter;
import android.graphics.BlurMaskFilter.Blur;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.EmbossMaskFilter;
import android.graphics.MaskFilter;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Join;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class JustDrawView extends View {

	private Paint mPaint;
	private Path    mPath;
	private Shapes mShapes;
	private ArrayList<Shapes> mShapesArray = new ArrayList<Shapes>();
	private ArrayList<Shapes> mShapesArrayUndo = new ArrayList<Shapes>();
	private int mLineWidth;
	private int mEffect;
	private int[] mColourValuesPen = new int[4];
	private int[] mColourValuesBackground = new int[4];

	private float mX, mY;
	private static final float TOUCH_TOLERANCE = 4;

	private Bitmap  mBitmap;        
	private Paint   mBitmapPaint;

	private MaskFilter mMaskFilter;
	private int mStrokeCap;
	private int mStrokeJoin;
	private int mStyle;
	//Shadow
	private boolean mShadow;
	private float mShadowDY;
	private float mShadowDX;
	private float mShadowRadius;
	//Blur
	private int mBlurStyle;
	private float mBlurRadius;
	//Emboss
	private float[] mEmbossDirection;
	private float mEmbossAmbient;
	private float mEmbossSpecular;
	private float mEmbossRadius;

	//Constructors
	public JustDrawView(Context context) {
		this(context, null, 0);
	}
	public JustDrawView(Context context, AttributeSet attributeSet)
	{
		this(context,attributeSet,0);
	}

	public JustDrawView(Context context, AttributeSet attrs, int defStyle) {

		super(context, attrs, defStyle);

		//Initialise background colour
		mColourValuesBackground[ColourPicker.COLOUR_VALUE_ALPHA] = 255;
		mColourValuesBackground[ColourPicker.COLOUR_VALUE_RED] = 255;
		mColourValuesBackground[ColourPicker.COLOUR_VALUE_BLUE] = 255;
		mColourValuesBackground[ColourPicker.COLOUR_VALUE_GREEN] = 255;
		setBackgroundColour(mColourValuesBackground);

		//Initialise pen colour
		mColourValuesPen[ColourPicker.COLOUR_VALUE_ALPHA] = 255;
		mColourValuesPen[ColourPicker.COLOUR_VALUE_RED] = 0;
		mColourValuesPen[ColourPicker.COLOUR_VALUE_BLUE] = 0;
		mColourValuesPen[ColourPicker.COLOUR_VALUE_GREEN] = 0;
		setLineColour(mColourValuesPen);

		//Initialise Width
		setLineWidth(6);

		//Initialise Effects
		setStyle(Constants.STYLE_STROKE);
		setStrokeCap(Constants.CAP_ROUND);
		setStrokeJoin(Constants.JOIN_ROUND);
		setEffect(Constants.EFFECT_NONE);
		setMaskFilter(null);

		//Blur Settings
		setBlurRadius(8f);
		setBlurStyle(Constants.BLUR_NORMAL);

		//Emboss Settings
		setEmbossDirection(new float[] { 1, 1, 1 });
		setEmbossAmbient(0.4f);
		setEmbossSpecular(6f);
		setEmbossRadius(3.5f);

		//Shadow Settings
		setShadow(false);
		setShadowRadius(5f);
		setShadowDX(5f);
		setShadowDY(5f);

		setPaint();

		mPath = new Path();
		mBitmapPaint = new Paint(Paint.DITHER_FLAG);
	}

	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);
		onClickNew(null);            
	}

	@Override
	protected void onDraw(Canvas canvas) {

		setPaint();

		int colour = Color.argb(mColourValuesBackground[ColourPicker.COLOUR_VALUE_ALPHA], mColourValuesBackground[ColourPicker.COLOUR_VALUE_RED], mColourValuesBackground[ColourPicker.COLOUR_VALUE_GREEN], mColourValuesBackground[ColourPicker.COLOUR_VALUE_BLUE]);
		canvas.drawColor(colour);
		canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);

		for (Shapes shapes : mShapesArray){
			canvas.drawPath(shapes.getPath(), shapes.getPaint());
		}
		canvas.drawPath(mPath, mPaint);

	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {

		float x = (int)event.getX();
		float y = (int)event.getY();

		switch (event.getAction()){
		case MotionEvent.ACTION_DOWN:
			//Clear our list of undone paths.
			mShapesArrayUndo.clear();
			touch_start(x,y);
			break;
		case MotionEvent.ACTION_UP:
			touch_up();
			break;
		case MotionEvent.ACTION_MOVE:
			touch_move(x,y);
			break;
		}
		invalidate();
		return true;
	}

	private void touch_start(float x, float y) {
		mPath.reset();
		mPath.moveTo(x, y);
		mX = x;
		mY = y;
	}
	private void touch_move(float x, float y) {
		float dx = Math.abs(x - mX);
		float dy = Math.abs(y - mY);
		if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
			mPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
			mX = x;
			mY = y;
		}
	}
	private void touch_up() {
		mPath.lineTo(mX, mY);
		mShapes = new Shapes(mPath, mPaint);
		mShapesArray.add(mShapes);
		mPath = new Path();
		setPaint();
	}

	public void setPaint(){
		Paint.Style style = null;
		Paint.Join join = null;
		Paint.Cap cap = null;

		switch(getStyle()){
		case Constants.STYLE_FILL:
			style = Paint.Style.FILL;
			break;
		case Constants.STYLE_FILL_AND_STROKE:
			style = Paint.Style.FILL_AND_STROKE;
			break;
		case Constants.STYLE_STROKE:
			style = Paint.Style.STROKE;
			break;
		default:
			style = Paint.Style.STROKE;
			break;
		}
		switch(getStrokeJoin()){
		case Constants.JOIN_BEVEL:
			join = Paint.Join.BEVEL;
			break;
		case Constants.JOIN_MITER:
			join = Paint.Join.MITER;
			break;
		case Constants.JOIN_ROUND:
			join = Paint.Join.ROUND;
			break;
		default:
			join = Paint.Join.ROUND;
			break;
		}
		switch(getStrokeCap()){
		case Constants.CAP_BUTT:
			cap = Paint.Cap.BUTT;
			break;
		case Constants.CAP_ROUND:
			cap = Paint.Cap.ROUND;
			break;
		case Constants.CAP_SQUARE:
			cap = Paint.Cap.SQUARE;
			break;
		default:
			cap = Paint.Cap.ROUND;
			break;
		}
		
		mPaint = new Paint();
		mPaint.setAntiAlias(true);
		mPaint.setStyle(style);
		mPaint.setStrokeJoin(join);
		mPaint.setStrokeCap(cap);
		mPaint.setStrokeWidth(getLineWidth());
		mPaint.setMaskFilter(getMaskFilter());
		if(isShadow()){
			mPaint.setShadowLayer(getShadowRadius(), getShadowDX(), getShadowDY(), Color.BLACK);
		}
		int colour = Color.argb(mColourValuesPen[ColourPicker.COLOUR_VALUE_ALPHA], mColourValuesPen[ColourPicker.COLOUR_VALUE_RED], mColourValuesPen[ColourPicker.COLOUR_VALUE_GREEN], mColourValuesPen[ColourPicker.COLOUR_VALUE_BLUE]);
		mPaint.setColor(colour);		
	}

	public void onClickUndo () { 
		if (mShapesArray.size()>0) {
			//Remove the last path from the main array and add it to the undo paths array.
			mShapesArrayUndo.add(mShapesArray.remove(mShapesArray.size()-1));
			invalidate();
		}
	}

	public void onClickRedo (){
		if (mShapesArrayUndo.size()>0) {
			//Remove the last path from the undo paths array and add it back into the main array.
			mShapesArray.add(mShapesArrayUndo.remove(mShapesArrayUndo.size()-1));
			invalidate();
		}  
	}

	public void onClickNew (Bitmap bitmap){
		mShapesArrayUndo.clear();
		mShapesArray.clear();
		if(bitmap==null){
			mBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
		}else{
			mBitmap = bitmap;	
		}
		invalidate();
	}

	public int getLineWidth() {
		return mLineWidth;
	}

	public void setLineWidth(int lineWidth) {
		this.mLineWidth = lineWidth;
	}

	public int[] getLineColour() {
		return mColourValuesPen;
	}

	public void setLineColour(int[] colour) {
		this.mColourValuesPen = colour;
	}

	public int[] getBackgroundColour() {
		return mColourValuesBackground;
	}

	public void setBackgroundColour(int[] colour) {
		this.mColourValuesBackground = colour;
		invalidate();
	}
	public int getEffect() {
		return mEffect;
	}
	public void setEffect(int effect) {
		this.mEffect = effect;
		switch(getEffect()){
		case Constants.EFFECT_BLUR:
			Blur blurStyle = null;
			switch (getBlurStyle()){
			case Constants.BLUR_INNER:
				blurStyle = Blur.INNER;
				break;
			case Constants.BLUR_NORMAL:
				blurStyle = Blur.NORMAL;
				break;
			case Constants.BLUR_OUTER:
				blurStyle = Blur.OUTER;
				break;
			case Constants.BLUR_SOLID:
				blurStyle = Blur.SOLID;
				break;
			default:
				blurStyle = Blur.NORMAL;
				break;
			}
			BlurMaskFilter blur = new BlurMaskFilter(getBlurRadius(), blurStyle);
			setMaskFilter(blur);
			break;
		case Constants.EFFECT_EMBOSS:
			EmbossMaskFilter emboss = new EmbossMaskFilter(getEmbossDirection(), getEmbossAmbient(), getEmbossSpecular(), getEmbossRadius());
			//EmbossMaskFilter emboss = new EmbossMaskFilter(new float[] { 1, 1, 1 }, 0.4f, 6, 3.5f);
			setMaskFilter(emboss);
			break;
		case Constants.EFFECT_NEON:
			break;
		case Constants.EFFECT_NONE:
			setMaskFilter(null);
			break;	
		}
	}
	private MaskFilter getMaskFilter() {
		return mMaskFilter;
	}
	private void setMaskFilter(MaskFilter maskFilter) {
		this.mMaskFilter = maskFilter;
	}
	public boolean isShadow() {
		return mShadow;
	}
	public void setShadow(boolean shadow) {
		this.mShadow = shadow;
	}
	public int getStyle() {
		return mStyle;
	}
	public void setStyle(int style) {
		this.mStyle = style;
	}
	public int getStrokeJoin() {
		return mStrokeJoin;
	}
	public void setStrokeJoin(int strokeJoin) {
		this.mStrokeJoin = strokeJoin;
	}
	public int getStrokeCap() {
		return mStrokeCap;
	}
	public void setStrokeCap(int strokeCap) {
		this.mStrokeCap = strokeCap;
	}
	public int getBlurStyle() {
		return mBlurStyle;
	}
	public void setBlurStyle(int blurStyle) {
		this.mBlurStyle = blurStyle;
	}
	public float getShadowDX() {
		return mShadowDX;
	}
	public void setShadowDX(float mShadowDX) {
		this.mShadowDX = mShadowDX;
	}
	public float getShadowDY() {
		return mShadowDY;
	}
	public void setShadowDY(float shadowDY) {
		this.mShadowDY = shadowDY;
	}
	public float getShadowRadius() {
		return mShadowRadius;
	}
	public void setShadowRadius(float mShadowRadius) {
		this.mShadowRadius = mShadowRadius;
	}
	public float getBlurRadius() {
		return mBlurRadius;
	}
	public void setBlurRadius(float mBlurRadius) {
		this.mBlurRadius = mBlurRadius;
	}
	public float[] getEmbossDirection() {
		return mEmbossDirection;
	}
	public void setEmbossDirection(float[] mEmbossDirection) {
		this.mEmbossDirection = mEmbossDirection;
	}
	public float getEmbossAmbient() {
		return mEmbossAmbient;
	}
	public void setEmbossAmbient(float mEmbossAmbient) {
		this.mEmbossAmbient = mEmbossAmbient;
	}
	public float getEmbossSpecular() {
		return mEmbossSpecular;
	}
	public void setEmbossSpecular(float mEmbossSpecular) {
		this.mEmbossSpecular = mEmbossSpecular;
	}
	public float getEmbossRadius() {
		return mEmbossRadius;
	}
	public void setEmbossRadius(float mEmbossRadius) {
		this.mEmbossRadius = mEmbossRadius;
	}
}
